
#pragma once
#include "Serial.hpp"

#include "nonstd/string.hpp"

// clang-format off
#define NUM_ELEMS(x) (sizeof(x)/sizeof(x[0]))
static const char* s_finalResponsesError[] = {
    "ERROR\r",
    "+CMS ERROR:",
    "+CME ERROR:", 
    "NO CARRIER", /* sometimes! */
    "NO ANSWER", 
    "NO DIALTONE",
};

/**
    * returns 1 if line is a final response indicating success
    * See 27.007 annex B
    * WARNING: NO CARRIER and others are sometimes unsolicited
    */
static const char* s_finalResponsesSuccess[] = {
    "OK\r", 
    "CONNECT" /* some stacks start up data on another channel */
};

//static const char* s_smsUnsoliciteds[] = {
//    "+CMT:",
//    "+CDS:",
//    "+CBM:",
//    "+CMTI:"
//};
// clang-format on

static auto at_finish = [](vstrings& vs) -> int {
    for (int i = (int)(vs.size() - 1); i >= 0; i--)
    {
        std::string s = vs.at(i);
        for (unsigned int i = 0; i < NUM_ELEMS(s_finalResponsesSuccess); i++)
        {
            if (nonstd::string::starts_with(s, s_finalResponsesSuccess[i]))
            {
                return 1;
            }
        }

        for (unsigned int i = 0; i < NUM_ELEMS(s_finalResponsesError); i++)
        {
            if (nonstd::string::starts_with(s, s_finalResponsesError[i]))
            {
                return 2;
            }
        }
    }
    return 0;
};

class ATChannel : public SimpleSerial
{
public:
    ATChannel(std::string name, int baudrate = 115200, char parity = 'N', int bits = 8, int stopbits = 1) :
        SimpleSerial(name.c_str(), baudrate, parity, bits, stopbits)
    {
        std::thread t2([&]() {
            notify_func     notify_fn     = std::bind([&]() {
                m_cv.notify_one();
            });
            is_enabled_func is_enabled_fn = std::bind([&]() {
                return at_start_flag.load() == 1;
            });

            handle_lines(
                [&](std::string& line) {
                    std::lock_guard<std::mutex> locker(mtx_);
                    m_lines.emplace_back(std::string(line));
                },
                is_enabled_fn, notify_fn);
        });
        t2.detach();
    }

    int at_handshake()
    {
        vstrings    vs;
        std::string s = "ATE0Q0V1\r\n";
        return at_transmit(s, vs);
    }

    int at_transmit(const std::string& cmd, vstrings& out_vs, bool print_log = true)
    {
        LOG_PRINT("AT << {}", std::string(cmd.data(), cmd.length() - 2));

        int result = transmit(cmd, out_vs);
        if (result == 0)
        {
            LOGE_PRINT(">> The AT Command Timeout expired.");
            std::for_each(out_vs.begin(), out_vs.end(), [&](auto s) {
                LOGE_PRINT("    AT >> {}", s);
            });
        }
        else if (result == 2)
        {
            LOGE_PRINT(">> The AT Command failed.");
            std::for_each(out_vs.begin(), out_vs.end(), [&](auto s) {
                LOGE_PRINT("    AT >> {}", s);
            });
        }
        else if (result == 1 && print_log)
        {
            std::for_each(out_vs.begin(), out_vs.end(), [&](auto s) {
                LOG_PRINT("AT >> {}", s);
            });
        }
        return result;
    }

    int at_transmit_multiline(const std::string& cmd, const std::string& prefix, vstrings& out_vs)
    {
        vstrings vs;

        int result = at_transmit(cmd, vs, false);
        if (result == 1)
        {
            std::string keyword(prefix);
            if (keyword.at(keyword.length() - 1) == '?')
            {
                keyword.at(keyword.length() - 1) = ':';
            }

            std::for_each(vs.begin(), vs.end(), [&](auto s) {
                if (nonstd::string::starts_with(s, keyword.c_str()))
                {
                    out_vs.emplace_back(s);
                }
            });

            if (out_vs.empty())
            {
                LOGE_PRINT("AT >> Couldn't find '{}'.", keyword);
                std::for_each(vs.begin(), vs.end(), [&](auto s) {
                    LOGE_PRINT("    >> {}", s);
                });
            }
            else
            {
                std::for_each(out_vs.begin(), out_vs.end(), [&](auto s) {
                    LOG_PRINT("AT >> {}", s);
                });
            }
        }
        return result;
    }

    int at_transmit_single(const std::string& cmd, const std::string& prefix, std::string& r)
    {
        vstrings vs;

        int result = at_transmit_multiline(cmd, prefix, vs);

        r = vs.size() > 0 ? vs.at(0) : "";

        return result;
    }

    void set_timeout(int timeout_ms, int interval_ms)
    {
        m_timeout_ms  = timeout_ms;
        m_interval_ms = interval_ms;
    }

    //-1:io error, 0:timeout, > 0: is_finish status
    int transmit(std::string s, vstrings& r)
    {
        vstrings().swap(r);

        at_start_flag.store(1);

        int result = -1;

        write(nonstd::span<char>{s.begin(), s.end()});

        {
            int frist_ms = m_timeout_ms;

            do
            {
                std::lock_guard<std::mutex> locker(mtx_);
                std::cv_status              status = m_cv.wait_for(mtx_, std::chrono::milliseconds(frist_ms));
                if (status == std::cv_status::timeout)
                {
                    result = 0;
                    break;
                }
                frist_ms = m_interval_ms;
            } while ((result = at_finish(m_lines)) <= 0);

            r.swap(m_lines);
        }

        at_start_flag.store(0);
        return result;
    }

private:
    int m_timeout_ms  = 10000;
    int m_interval_ms = 500;

    std::atomic_int at_start_flag{0};

    std::condition_variable_any m_cv;
    vstrings                    m_lines;
    std::mutex                  mtx_;
};
